/*
*    l3qd - Light, light, lightweight queue
*    Copyright (C) 2024  Marcus Pedersén marcus@marcux.org
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package validate

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/spf13/cobra"
	"golang.org/x/term"
	"io"
	"notabug.org/marcux/l3q/internal/client"
	"notabug.org/marcux/l3q/internal/common"
	"os"
	"os/user"
	"path/filepath"
	"strconv"
	"syscall"
)

var validateCmdFlagPort uint
var validateCmdFlagVerbose bool

var ValidateCmd = &cobra.Command{
	Use:   "validate",
	Short: "Configure and validate client with l3q daemon.",
	Long: `Description:
  Configure and validate client with l3q daemon.
  Without validation the client on this host will not be able to communicate with daemon.
  Start the validation server on the same server as the l3q daemon with: 
  l3qd --validate-host
  and follow instructions.`,
	Run: func(cmd *cobra.Command, args []string) {
		if validateCmdFlagPort <= 0 {
			fmt.Fprintf(os.Stderr, "Flag --port is required.\nTry -h or --help for help.\n")
			os.Exit(1)
		}

		validate()
	},
}

// validates this client with
// l3q daemon. Saves key to file.
func validate() {
	// User must be root to run command
	usr, err := user.Current()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to get current user: %s\n", err)
		os.Exit(1)
	}

	if usr.Uid != "0" {
		fmt.Fprintf(os.Stderr, "Permission denied\n")
		os.Exit(1)
	}

	conf := client.GetConfig(validateCmdFlagVerbose)

	dir := filepath.Dir(conf.L3qValidateFile)
	_, err = os.Stat(dir)
	if errors.Is(err, os.ErrNotExist) {
		fmt.Fprintf(os.Stderr, "Directory for validate key: %s, does not exist\n", dir)
		os.Exit(1)
	}

	grp, err := user.LookupGroup(conf.L3qGroup)
	if err != nil {
		switch err.(type) {
		case user.UnknownGroupError:
			fmt.Fprintf(os.Stderr, "Group: %s, does not exist.\nCreate group and try again.\n", conf.L3qGroup)
		default:
			fmt.Fprintf(os.Stderr, "Failed to lookup group: %s, %s\n", conf.L3qGroup, err)
		}
		os.Exit(1)
	}
	fmt.Println("Please make sure that you have started the validation daemon")
	fmt.Println("on the same server as the l3q daemon runs.")
	fmt.Println("Start validation daemon with command:")
	fmt.Println("l3qd --validate-host")
	fmt.Println("")

	var passwd string
	for {
		fmt.Print("Enter password, same as entered on l3q validation daemon: ")
		bytepw, err := term.ReadPassword(int(syscall.Stdin))
		if err != nil {
			fmt.Fprintf(os.Stderr, "\nFailed to read password: %s, please try again.\n\n", err)
			continue
		}

		fmt.Println("")
		passwd = string(bytepw)
		break
	}

	url := fmt.Sprintf("https://%s:%d/api/v1/validate/client?password=%s", conf.L3qdHost, validateCmdFlagPort, passwd)

	httpClient := client.SkipVerifyHttpClient()
	resp, err := httpClient.Get(url)
	if err != nil {
		if validateCmdFlagVerbose {
			fmt.Fprintf(os.Stderr, "Failed to connect to server: %s\n%s\n", url, err)
		} else {
			fmt.Fprintf(os.Stderr, "Failed to connect to server: %s\n", url)
		}
		os.Exit(1)
	}

	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		if validateCmdFlagVerbose {
			fmt.Fprintf(os.Stderr, "Error in response from server: %s\n%s\n", url, resp.Status)
		} else {
			fmt.Fprintf(os.Stderr, "Error in response from server: %s\n", url)
		}
		os.Exit(1)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		if validateCmdFlagVerbose {
			fmt.Fprintf(os.Stderr, "Failed to read body in response from server: %s\n%s\n", url, err)
		} else {
			fmt.Fprintf(os.Stderr, "Failed to read body in response from server: %s\n", url)
		}
		os.Exit(1)
	}

	keyResp := common.KeyResponse{}
	err = json.Unmarshal(body, &keyResp)
	if err != nil {
		if validateCmdFlagVerbose {
			fmt.Fprintf(os.Stderr, "Failed to parse json respons from server, url: %s\n%s\n", url, err)
		} else {
			fmt.Fprintf(os.Stderr, "Failed to parse json respons from server, url: %s\n", url)
		}
		os.Exit(1)
	}

	gid, err := strconv.Atoi(grp.Gid)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to convert gid to integer, %s", err)
		os.Exit(1)
	}

	saveKeyToFile(conf.L3qValidateFile, keyResp.Key, gid)
}

// Key is written to file
// if file already exist
// it will be truncated
// and new key will be
// written to file
func saveKeyToFile(filePath string, key string, gid int) {
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0640)
	if err != nil {
		if validateCmdFlagVerbose {
			fmt.Fprintf(os.Stderr, "Failed to open file: %s, %s\n", filePath, err)
		} else {
			fmt.Fprintf(os.Stderr, "Failed to open file: %s\n", filePath)
		}
		os.Exit(1)
	}

	defer file.Close()

	_, err = file.WriteString(key)
	if err != nil {
		if validateCmdFlagVerbose {
			fmt.Fprintf(os.Stderr, "Failed to write to file: %s, %s\n", filePath, err)
		} else {
			fmt.Fprintf(os.Stderr, "Failed to write to file: %s\n", filePath)
		}
		os.Exit(1)
	}

	err = os.Chown(filePath, 0, gid)
	if err != nil {
		if validateCmdFlagVerbose {
			fmt.Fprintf(os.Stderr, "Failed to change owner on file: %s, %s\n", filePath, err)
		} else {
			fmt.Fprintf(os.Stderr, "Failed to change owner on file: %s\n", filePath)
		}
	}

	fmt.Println("Validation SUCCESS!!")
}

func init() {
	ValidateCmd.Flags().UintVarP(&validateCmdFlagPort, "port", "p", 0, `Port that the validate daemon is listening on, on l3qd server.
Port is specified when validation daemon is started.`)
	ValidateCmd.Flags().BoolVarP(&validateCmdFlagVerbose, "verbose", "v", false, "Print verbose output")
}
